﻿/****************************************************************************************************
	Copyright (C) 2010 RapidWebDev Organization (http://rapidwebdev.org)
	Author: Eunge, Legal Name: Jian Liu, Email: eunge.liu@RapidWebDev.org

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
 ****************************************************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text.RegularExpressions;
using System.Transactions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using BaoJianSoft.Common;
using BaoJianSoft.Common.Globalization;
using BaoJianSoft.Common.Data;
using BaoJianSoft.ExtensionModel.Web.Controls;
using BaoJianSoft.Platform.Initialization;
using BaoJianSoft.Platform.Linq;
using BaoJianSoft.Platform.Properties;
using BaoJianSoft.Platform.Web.Controls;
using BaoJianSoft.RapidWeb.DynamicPages;
using AspNetMembership = System.Web.Security.Membership;
using PermissionTreeView = BaoJianSoft.Platform.Web.Controls.PermissionTreeView;
using BaoJianSoft.Common.Validation;

namespace BaoJianSoft.Platform.Web.DynamicPage
{
	/// <summary>
	/// User detail panel page handler.
	/// </summary>
	public class UserDetailPanel : DetailPanelPage
	{
		private static object syncObject = new object();
		protected static IAuthenticationContext authenticationContext = SpringContext.Current.GetObject<IAuthenticationContext>();
		protected static IMembershipApi membershipApi = SpringContext.Current.GetObject<IMembershipApi>();
		protected static IPlatformConfiguration platformConfiguration = SpringContext.Current.GetObject<IPlatformConfiguration>();
		protected static IRoleApi roleApi = SpringContext.Current.GetObject<IRoleApi>();
		protected static IPermissionApi permissionApi = SpringContext.Current.GetObject<IPermissionApi>();
		protected static IOrganizationApi organizationApi = SpringContext.Current.GetObject<IOrganizationApi>();

		private List<CheckBox> dynamicGeneratedCheckBox = new List<CheckBox>();

		#region Protected Web Controls

		[Binding("TabContainer/TabPanelProfile")]
		protected OrganizationSelector OrganizationSelector;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxUserName;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxPassword;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxConfirmPassword;

		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxDisplayName;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxEmail;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxMobile;

		[Binding("TabContainer/TabPanelProfile")]
		protected ExtensionDataForm UserExtensionDataForm;

		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxComment;
		[Binding("TabContainer/TabPanelProfile")]
		protected RadioButtonList RadioButtonListStatus;
		[Binding("TabContainer/TabPanelProfile")]
		protected PlaceHolder PlaceHolderOperateContext;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxCreationDate;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxLastLoginDate;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxLastActivityDate;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxLockedOutDate;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxLastPasswordChangedDate;
		[Binding("TabContainer/TabPanelProfile")]
		protected TextBox TextBoxLastUpdatedDate;

		[Binding("TabContainer/TabPanelProfile")]
		protected Panel PanelRoleContainer;

		[Binding("TabContainer/TabPanelPermission")]
		protected PermissionTreeView @PermissionTreeView;

		[Binding("TabContainer")]
		protected AjaxControlToolkit.TabPanel TabPanelProfile;
		[Binding("TabContainer")]
		protected AjaxControlToolkit.TabPanel TabPanelPermission;

		#endregion

		/// <summary>
		/// Gets organization domain
		/// </summary>
		protected string Domain
		{
			get { return authenticationContext.TempVariables["Domain.Value"] as string; }
		}

		/// <summary>
		/// Create a new user from detail panel and return the id.
		/// The method needs to create a new entity and set control values to its properties then persist it.
		/// </summary>
		/// <returns>returns the user id after it's created successfully.</returns>
		public override string Create()
		{
			this.ValidateDataInputForm(null);

			using (RwdTransactionScope ts = new RwdTransactionScope())
			{
				UserObject userObject = new UserObject();
				this.UserExtensionDataForm.SetObjectPropertiesFromControlValues(userObject);

				this.SetUserPropertiesFromControls(userObject);

				string password = null;
				if (this.TextBoxPassword != null)
					password = this.TextBoxPassword.Text;

				membershipApi.Save(userObject, password, null);
				Guid userId = userObject.UserId;

				if (this.OrganizationSelector != null)
				{
					OrganizationObject organizationObject = organizationApi.GetOrganization(this.OrganizationSelector.SelectedOrganization.OrganizationId);
					IEnumerable<Guid> roleIds = this.ResolveSelectedRoleCheckBox(organizationObject);
					roleApi.SetUserToRoles(userId, roleIds);
				}

				if (this.PermissionTreeView != null)
					permissionApi.SetUserPermissions(userId, @PermissionTreeView.CheckedValues);

				ts.Complete();
				return userId.ToString();
			}
		}

		/// <summary>
		/// Update an existed organization type from detail panel.
		/// The method needs to load an existed entity by specified id and set control values to overwrite its original properties then persist it.
		/// </summary>
		/// <param name="entityId"></param>
		public override void Update(string entityId)
		{
			this.ValidateDataInputForm(new Guid(entityId));

			using (RwdTransactionScope ts = new RwdTransactionScope())
			{
				UserObject userObject = membershipApi.Get(new Guid(entityId));
				this.UserExtensionDataForm.SetObjectPropertiesFromControlValues(userObject);

				this.SetUserPropertiesFromControls(userObject);

				string password = null;
				if (this.TextBoxPassword != null)
					password = this.TextBoxPassword.Text;

				membershipApi.Save(userObject, password, null);

				if (this.OrganizationSelector != null)
				{
					OrganizationObject organizationObject = organizationApi.GetOrganization(this.OrganizationSelector.SelectedOrganization.OrganizationId);
					IEnumerable<Guid> roleIds = this.ResolveSelectedRoleCheckBox(organizationObject);
					roleApi.SetUserToRoles(userObject.UserId, roleIds);
				}

				if (this.PermissionTreeView != null)
					permissionApi.SetUserPermissions(userObject.UserId, @PermissionTreeView.CheckedValues);

				ts.Complete();
			}
		}

		/// <summary>
		/// Reset all controls of the detail panel to initial state.
		/// The method will be invoked when enables the detail panel to support creating entities continuously.
		/// After an entity been created, the method will be invoked to reset form controls for another input.
		/// </summary>
		public override void Reset()
		{
			if (this.OrganizationSelector != null)
				this.OrganizationSelector.SelectedOrganization = null;

			if (this.TextBoxUserName != null)
				this.TextBoxUserName.Text = "";

			if (this.TextBoxPassword != null)
				this.TextBoxPassword.Text = "";

			if (this.TextBoxConfirmPassword != null)
				this.TextBoxConfirmPassword.Text = "";

			if (this.TextBoxDisplayName != null)
				this.TextBoxDisplayName.Text = "";

			if (this.TextBoxEmail != null)
				this.TextBoxEmail.Text = "";

			if (this.TextBoxMobile != null)
				this.TextBoxMobile.Text = "";

			this.UserExtensionDataForm.ResetControlValuesToDefault();

			if (this.TextBoxComment != null)
				this.TextBoxComment.Text = "";

			if (this.RadioButtonListStatus != null)
				this.RadioButtonListStatus.SelectedValue = true.ToString();

			if (this.PlaceHolderOperateContext != null)
				this.PlaceHolderOperateContext.Visible = false;

			if (this.TextBoxCreationDate != null)
				this.TextBoxCreationDate.Text = "";

			if (this.TextBoxLastLoginDate != null)
				this.TextBoxLastLoginDate.Text = "";

			if (this.TextBoxLastActivityDate != null)
				this.TextBoxLastActivityDate.Text = "";

			if (this.TextBoxLockedOutDate != null)
				this.TextBoxLockedOutDate.Text = "";

			if (this.TextBoxLastPasswordChangedDate != null)
				this.TextBoxLastPasswordChangedDate.Text = "";

			if (this.TextBoxLastUpdatedDate != null)
				this.TextBoxLastUpdatedDate.Text = "";

			if (this.PanelRoleContainer != null)
				this.PanelRoleContainer.Controls.Clear();

			if (this.PermissionTreeView != null)
				this.PermissionTreeView.CheckedValues = new string[0];

			if (this.TextBoxUserName != null)
				ScriptManager.GetCurrent(HttpContext.Current.Handler as Page).SetFocus(this.TextBoxUserName);
		}

		/// <summary>
		/// The method is designed to load entity by id to editable detail panel controls.
		/// </summary>
		/// <param name="entityId"></param>
		public override void LoadWritableEntity(string entityId)
		{
			Guid userId = new Guid(entityId);

			UserObject userObject = membershipApi.Get(userId);
			Guid organizationId = userObject.OrganizationId;
			OrganizationObject organizationObject = organizationId != Guid.Empty ? organizationApi.GetOrganization(organizationId) : null;

			if (this.OrganizationSelector != null)
				this.OrganizationSelector.SelectedOrganization = organizationObject;

			this.CreateRoleCheckBoxes(organizationObject);

			if (this.TextBoxUserName != null)
				this.TextBoxUserName.Text = userObject.UserName;

			if (this.TextBoxPassword != null)
				this.TextBoxPassword.Text = "";

			if (this.TextBoxConfirmPassword != null)
				this.TextBoxConfirmPassword.Text = "";

			IEnumerable<Guid> roleIds = roleApi.FindByUserId(userObject.UserId).Select(roleObject => roleObject.RoleId);
			this.SetRoleCheckBoxSelection(roleIds);

			if (this.TextBoxDisplayName != null)
				this.TextBoxDisplayName.Text = userObject.DisplayName;

			if (this.TextBoxEmail != null)
				this.TextBoxEmail.Text = userObject.Email;

			if (this.TextBoxMobile != null)
				this.TextBoxMobile.Text = userObject.MobilePin;

			if (this.TextBoxComment != null)
				this.TextBoxComment.Text = userObject.Comment;

			if (this.RadioButtonListStatus != null)
				this.RadioButtonListStatus.SelectedValue = userObject.IsApproved.ToString();

			// extension properties
			if (this.UserExtensionDataForm != null)
				this.UserExtensionDataForm.SetControlValuesFromObjectProperties(userObject);

			if (this.PlaceHolderOperateContext != null)
				this.PlaceHolderOperateContext.Visible = true;
			DateTime emptyStringWhenEarlierEqualThanDate = new DateTime(1970, 1, 1);
			if (this.TextBoxCreationDate != null)
				this.TextBoxCreationDate.Text = LocalizedDateTime.ToDateTimeString(userObject.CreationDate, emptyStringWhenEarlierEqualThanDate);
			if (this.TextBoxLastLoginDate != null)
				this.TextBoxLastLoginDate.Text = LocalizedDateTime.ToDateTimeString(userObject.LastLoginDate, emptyStringWhenEarlierEqualThanDate);
			if (this.TextBoxLastActivityDate != null)
				this.TextBoxLastActivityDate.Text = LocalizedDateTime.ToDateTimeString(userObject.LastActivityDate, emptyStringWhenEarlierEqualThanDate);
			if (this.TextBoxLockedOutDate != null)
				this.TextBoxLockedOutDate.Text = LocalizedDateTime.ToDateTimeString(userObject.LastLockoutDate, emptyStringWhenEarlierEqualThanDate);
			if (this.TextBoxLastPasswordChangedDate != null)
				this.TextBoxLastPasswordChangedDate.Text = LocalizedDateTime.ToDateTimeString(userObject.LastPasswordChangedDate, emptyStringWhenEarlierEqualThanDate);
			if (this.TextBoxLastUpdatedDate != null)
				this.TextBoxLastUpdatedDate.Text = LocalizedDateTime.ToDateTimeString(userObject.LastUpdatedDate, emptyStringWhenEarlierEqualThanDate);

			if (this.PermissionTreeView != null)
			{
				IEnumerable<PermissionObject> permissions = permissionApi.FindUserPermissions(userObject.UserId, true);
				this.PermissionTreeView.CheckedValues = permissions.Select(p => p.PermissionValue);
			}
		}

		/// <summary>
		/// The method is designed to load entity by id to readonly detail panel controls.
		/// </summary>
		/// <param name="entityId"></param>
		public override void LoadReadOnlyEntity(string entityId)
		{
			base.LoadReadOnlyEntity(entityId);

			if (this.PanelRoleContainer != null)
			{
				// if there has roles not associated with organization type but associated with the user by business requirements,
				// here should load roles explicitly associated with the user and merge them with roles binding to the organization type.
				IEnumerable<RoleObject> rolesAssociatedWithUser = roleApi.FindByUserId(new Guid(entityId));
				foreach (RoleObject roleAssociatedWithUser in rolesAssociatedWithUser)
				{
					string checkBoxId = string.Format("ID{0}", roleAssociatedWithUser.RoleId);
					if (this.dynamicGeneratedCheckBox.Count(cb => cb.ID == checkBoxId) == 0)
					{
						CheckBox checkBox4TheRoleAssociatedWithUser = new CheckBox
						{
							ID = checkBoxId,
							Text = roleAssociatedWithUser.RoleName,
							Checked = true
						};

						this.dynamicGeneratedCheckBox.Add(checkBox4TheRoleAssociatedWithUser);
						this.PanelRoleContainer.Controls.Add(checkBox4TheRoleAssociatedWithUser);
					}
				}

				// remove unchecked role checkbox in view UI.
				this.dynamicGeneratedCheckBox.ForEach(checkbox => checkbox.Enabled = false);
				foreach (CheckBox uncheckedCheckBox in this.dynamicGeneratedCheckBox.Where(cb => !cb.Checked))
					this.PanelRoleContainer.Controls.Remove(uncheckedCheckBox);
			}

			if (this.TabPanelPermission != null)
				this.TabPanelPermission.Visible = false;
		}

		/// <summary>
		/// The method will be invoked when detail panel is loaded.
		/// The implementation can resolve current visiting Page from the conversation as (HttpContext.Current.Handler as Page).
		/// </summary>
		/// <param name="sender">The web page which contains the detail panel.</param>
		/// <param name="e">Callback event argument.</param>
		public override void OnLoad(object sender, DetailPanelPageEventArgs e)
		{
			if (this.UserExtensionDataForm != null)
			{
				Guid extensionDataTypeId = this.ResolveUserExtensionDataTypeId();
				this.UserExtensionDataForm.CreateDataInputForm(extensionDataTypeId);
			}

			this.RegisterClientAssociationBetweenOrganizationAndRole();

			if (this.OrganizationSelector != null)
			{
				this.OrganizationSelector.Domain = this.Domain;
				if (this.OrganizationSelector.SelectedOrganization != null)
				{
					OrganizationObject organizationObject = this.OrganizationSelector.SelectedOrganization;
					this.CreateRoleCheckBoxes(organizationObject);

					IEnumerable<Guid> selectedRoleIds = this.ResolveSelectedRoleCheckBox(organizationObject);
					this.SetRoleCheckBoxSelection(selectedRoleIds);
				}
			}
		}

		/// <summary>
		/// Setup context temporary variables for formatting configured text-typed properties.
		/// Set domain into http context when web page is initializing.
		/// </summary>
		/// <param name="sender">The sender which invokes the method.</param>
		/// <param name="e">Callback event argument.</param>
		public override void SetupContextTempVariables(object sender, SetupApplicationContextVariablesEventArgs e)
		{
			SetupContextTempVariablesUtility.SetupOrganizationDomain(e);
		}

		/// <summary>
		/// Resolve user extension data type id by default xml configured metadata type.
		/// </summary>
		/// <returns></returns>
		protected virtual Guid ResolveUserExtensionDataTypeId()
		{
			return ExtensionModel.ExtensionObjectFactory.Create<User>().ExtensionDataTypeId;
		}

		/// <summary>
		/// Set controls from user fixed properties, e.g. UserName, DisplayName, Email, Comment, IsApproved, Mobile and OrganizationId.
		/// </summary>
		/// <param name="userObject"></param>
		protected virtual void SetUserPropertiesFromControls(UserObject userObject)
		{
			if (this.TextBoxUserName != null)
				userObject.UserName = this.TextBoxUserName.Text;
			if (this.TextBoxDisplayName != null)
				userObject.DisplayName = this.TextBoxDisplayName.Text;
			if (this.TextBoxEmail != null)
				userObject.Email = this.TextBoxEmail.Text;
			if (this.TextBoxComment != null)
				userObject.Comment = this.TextBoxComment.Text;
			if (this.RadioButtonListStatus != null)
				userObject.IsApproved = bool.Parse(RadioButtonListStatus.SelectedValue);
			if (this.TextBoxMobile != null)
				userObject.MobilePin = this.TextBoxMobile.Text;
			if (this.OrganizationSelector != null)
				userObject.OrganizationId = this.OrganizationSelector.SelectedOrganization.OrganizationId;
		}

		/// <summary>
		/// Validate data input from client.
		/// </summary>
		/// <param name="userId"></param>
		protected virtual void ValidateDataInputForm(Guid? userId)
		{
			using (ValidationScope validationScope = new ValidationScope())
			{
				int recordCount;
				Expression<Func<User, bool>> predicate;

				if (this.OrganizationSelector != null && this.OrganizationSelector.SelectedOrganization == null)
					validationScope.Error(Resources.OrganizationCannotBeEmpty);

				if (this.TextBoxUserName != null && this.TextBoxUserName.Text.Trim().Length == 0)
					validationScope.Error(Resources.UserNameCannotBeEmpty);
				else if (this.TextBoxUserName != null)
				{
					predicate = u => u.UserName == this.TextBoxUserName.Text && u.UserId != userId;
					membershipApi.FindUsers(predicate, null, null, 0, int.MaxValue, out recordCount);
					if (recordCount > 0)
						validationScope.Error(Resources.DuplicateUserName, this.TextBoxUserName.Text);
				}

				if (this.TextBoxPassword != null && !userId.HasValue && this.TextBoxPassword.Text.Trim().Length == 0)
					validationScope.Error(Resources.PasswordCannotBeEmpty);

				if (this.TextBoxPassword != null && this.TextBoxPassword.Text.Trim().Length > 0)
				{
					if (this.TextBoxPassword.Text.Trim() != this.TextBoxConfirmPassword.Text.Trim())
						validationScope.Error(Resources.PasswordNotEqualToConfirmPassword);
					else
					{
						if (this.TextBoxPassword.Text.Trim().Length < AspNetMembership.MinRequiredPasswordLength)
							validationScope.Error(Resources.PasswordLengthLessThanRequired, AspNetMembership.MinRequiredPasswordLength);

						if (!Kit.IsEmpty(AspNetMembership.PasswordStrengthRegularExpression))
						{
							Regex regex = new Regex(AspNetMembership.PasswordStrengthRegularExpression, RegexOptions.Compiled);
							if (!regex.IsMatch(TextBoxPassword.Text.Trim()))
								validationScope.Error(Resources.PasswordFormatIsInvalid, AspNetMembership.PasswordStrengthRegularExpression);
						}
					}
				}

				if (this.TextBoxDisplayName != null && this.TextBoxDisplayName.Text.Trim().Length == 0)
					validationScope.Error(Resources.DisplayNameCannotBeEmpty);
				else if (this.TextBoxDisplayName != null)
				{
					predicate = u => u.DisplayName == this.TextBoxDisplayName.Text && u.UserId != userId;
					membershipApi.FindUsers(predicate, null, null, 0, int.MaxValue, out recordCount);
					if (recordCount > 0)
						validationScope.Error(Resources.DuplicateDisplayName, this.TextBoxDisplayName.Text);
				}
			}
		}

		#region Role CheckBox Populated by Organization Selection

		private void CreateRoleCheckBoxes(OrganizationObject organization)
		{
			if (this.PanelRoleContainer == null) return;

			this.PanelRoleContainer.Controls.Clear();
			if (organization == null) return;

			IEnumerable<RoleObject> roleObjects = this.FindRolesByOrganization(organization);
			foreach (RoleObject roleObject in roleObjects)
			{
				CheckBox checkbox = new CheckBox
				{
					ID = string.Format("ID{0}", roleObject.RoleId),
					Text = roleObject.RoleName
				};

				this.PanelRoleContainer.Controls.Add(checkbox);
				this.dynamicGeneratedCheckBox.Add(checkbox);
			}
		}

		private void SetRoleCheckBoxSelection(IEnumerable<Guid> roleIds)
		{
			if (this.PanelRoleContainer == null) return;

			foreach (CheckBox checkBox in this.PanelRoleContainer.Controls)
			{
				if (checkBox != null) checkBox.Checked = false;
			}

			foreach (Guid roleId in roleIds)
			{
				string checkBoxId = string.Format("ID{0}", roleId);
				CheckBox checkBox = this.PanelRoleContainer.FindControl(checkBoxId) as CheckBox;
				if (checkBox != null)
					checkBox.Checked = true;
			}
		}

		private IEnumerable<Guid> ResolveSelectedRoleCheckBox(OrganizationObject organization)
		{
			List<Guid> roleIds = new List<Guid>();
			if (organization == null) return roleIds;

			IEnumerable<RoleObject> roleObjects = this.FindRolesByOrganization(organization);
			foreach (RoleObject roleObject in roleObjects)
			{
				string name = string.Format("{0}$ID{1}", TabPanelProfile.UniqueID, roleObject.RoleId);
				string checkBoxValue = HttpContext.Current.Request.Params[name];
				if (string.Equals(checkBoxValue, "on", StringComparison.InvariantCultureIgnoreCase))
					roleIds.Add(roleObject.RoleId);
			}

			return roleIds;
		}

		private IEnumerable<RoleObject> FindRolesByOrganization(OrganizationObject organizationObject)
		{
			int recordCount;
			Expression<Func<Role, bool>> predicate = role => role.RolesInOrganizationTypes.Any(rolesInOrganizationType => rolesInOrganizationType.OrganizationTypeId == organizationObject.OrganizationTypeId);
			IEnumerable<RoleObject> results = roleApi.FindRoles(predicate, null, "RoleName ASC", 0, int.MaxValue, out recordCount);
			return results.ToList();
		}

		private void RegisterClientAssociationBetweenOrganizationAndRole()
		{
			if (this.OrganizationSelector != null && this.PanelRoleContainer != null)
			{
				string javascript = "window." + this.PanelRoleContainer.ClientID + " = new RolesByOrganization('" + this.PanelRoleContainer.ClientID + "', '" + this.TabPanelProfile.ClientID + "');";
				ScriptManager.RegisterStartupScript(this.OrganizationSelector, this.OrganizationSelector.GetType(), javascript.GetHashCode().ToString(), javascript, true);

				this.OrganizationSelector.SelectedOrganizationChangedCallback = string.Format("window.{0}.selectedOrganizationChangedCallback", this.PanelRoleContainer.ClientID);
			}
		}

		#endregion
	}
}
